﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace DDD {
    /// <summary>
    /// 頂点データを保持するコンテナ
    /// </summary>
    /// <remarks>
    /// 頂点データを保持しAttribute変数としてシェーダーに送られます。
    /// 原則エンジン側では頂点データの持つ意味(セマンティクス)は考慮しませんが、
    /// 座標位置(Positions)と法線(Normals)の2つだけはエンジン側でも使用します。
    /// 同名で型に依存するクラスがあります。
    /// </remarks>
    public abstract class VertexArray : Object {

        #region Field
        int vertexCount;
        int componentCount;
        ComponentType type;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="numVertices">頂点数</param>
        /// <param name="numComponents">1頂点あたりのコンポーネント数</param>
        /// <param name="type">データの型</param>
        public VertexArray (int numVertices, int numComponents, Type type) {

            this.vertexCount = numVertices;
            this.componentCount = numComponents;

            // この下激しくださいので何とかする

            if (type == typeof (byte)) {
                this.type = ComponentType.Byte;
            }
            if (type == typeof (sbyte)) {
                this.type = ComponentType.SByte;
            }
            if (type == typeof (short)) {
                this.type = ComponentType.Short;
            }
            if (type == typeof (ushort)) {
                this.type = ComponentType.UShort;
            }
            if (type == typeof (int)) {
                this.type = ComponentType.Int;
            }
            if (type == typeof (uint)) {
                this.type = ComponentType.UInt;
            }
            if (type == typeof (float)) {
                this.type = ComponentType.Float;
            }

        }
        #endregion

        #region Property
        /// <summary>
        /// 頂点の数
        /// </summary>
        /// <remarks>
        /// この頂点データを構成する頂点の数です。
        /// </remarks>
        public int VertexCount {
            get { return vertexCount; }
        }

        /// <summary>
        /// 1頂点あたりのコンポーネント数
        /// </summary>
        /// <remarks>
        /// </remarks>
        public int ComponentCount {
            get { return componentCount; }
        }

        /// <summary>
        /// 頂点データの要素の型
        /// </summary>
        /// <remarks>
        /// </remarks>
        public ComponentType ComponentType {
            get { return type; }
        }

        /// <summary>
        /// このオブジェクトが参照する全DDDオブジェクトを列挙する列挙子
        /// </summary>
        /// <remarks>
        /// 参照にnullは含まれません。同一のオブジェクトが複数出現する可能性があります。
        /// </remarks>
        public override IEnumerable<Object> References {
            get {
                return base.References;
            }
        }
        #endregion

        #region Method

        /// <summary>
        /// 頂点データをfloatで取得します
        /// </summary>
        /// <remarks>
        /// 頂点データを内部の型にかかわらずfloatで取得します。
        /// </remarks>
        /// <param name="firstVertex">取得を始める頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="dst">結果を書き込むfloatの配列</param>
        /// public abstract void Get (int firstVertex, int numVertices, float[] dst);

        /// <summary>
        /// 頂点データをfloatでセットします
        /// </summary>
        /// <remarks>
        /// </remarks>
        /// <param name="firstVertex">書き込みを始める頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="src">値を読み取るfloatの配列</param>
        /// public abstract void Set (int firstVertex, int numVertices, float[] src);

        public abstract void Set (int firstVertex, int numVertices, IntPtr src);

        #endregion
    }

    /// <summary>
    /// 頂点データを保持するコンテナ
    /// </summary>
    /// <remarks>
    /// 頂点データを保持するコンテナクラスのうち型に依存するクラスです。
    /// 頂点データの型(<typeparamref name="TValue"/>)として使用できるのは、
    /// <dic>byte</dic>,<dic>short</dic>,<dic>ushort</dic>,<dic>int</dic>,<dic>uint</dic>,<dic>float</dic>の6つです。
    /// </remarks>
    /// <typeparam name="TValue">頂点データの型</typeparam>
    public class VertexArray<TValue> : VertexArray, IEnumerable<TValue> where TValue : struct {

        #region Field
        TValue[] buffer;
        #endregion

        #region Constructor
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <remarks>
        /// 指定の頂点数、コンポーネント数のbyte, sbyte, int,uint,short,ushort,float型のデータを保持するコンテナクラスです。
        /// </remarks>
        /// <param name="numVertices">頂点数</param>
        /// <param name="numComponents">1頂点あたりのコンポーネント数(1,2,3,4)</param>
        public VertexArray (int numVertices, int numComponents)
            : base (numVertices, numComponents, typeof (TValue)) {
            if (numVertices <= 0 || numVertices >= Int32.MaxValue) {
                throw new ArgumentException ("Number of vertices is invalid");
            }
            if (numComponents < 1 || numComponents > 4) {
                throw new ArgumentException ("Number of components is invalid");
            }
            if (!(typeof (TValue) == typeof (byte) || typeof (TValue) == typeof (sbyte) || 
                typeof (TValue) == typeof (int) || typeof (TValue) == typeof (uint) ||
                typeof (TValue) == typeof (short) || typeof (TValue) == typeof (ushort) ||
                typeof (TValue) == typeof (float))) {
                throw new ArgumentException ("Type of VertexArray is invalid");
            }

            this.buffer = new TValue[numVertices * numComponents];
        }
        #endregion

        #region Property
        /// <summary>
        /// 全てのコンポーネントを列挙する列挙子
        /// </summary>
        public IEnumerable<TValue> Components {
            get { return buffer; }
        }
        #endregion

        #region Method

        public void Clear () {
            for (var i = 0; i < buffer.Length; i++) {
                this.buffer[i] = default (TValue);
            }
        }

        /// <summary>
        /// <dic>IEnumerable{TValue}</dic>インターフェースの実装
        /// </summary>
        /// <remarks>
        /// このメソッドは格納されている生データを列挙します。
        /// </remarks>
        /// <returns></returns>
        IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator () {
            return ((IEnumerable<TValue>)buffer).GetEnumerator ();
        }

        /// <summary>
        /// <dic>IEnumerable</dic>インターフェースの実装
        /// </summary>
        /// <remarks>
        /// このメソッドは格納されている生データを列挙します。
        /// </remarks>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
            return buffer.GetEnumerator ();
        }

        /// <summary>
        /// 頂点データの取得します
        /// </summary>
        /// <param name="firstVertex">取得を始める最初の頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="dst">結果を書き込む<typeparamref name="TValue"/>型の配列</param>
        public void Get (int firstVertex, int numVertices, TValue[] dst) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (dst == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (dst.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Dst length is too short");
            }

            Array.Copy (buffer, firstVertex * ComponentCount, dst, 0, numVertices * ComponentCount);
        }

        /*
        /// <summary>
        /// 頂点データをfloatで取得します
        /// </summary>
        /// <remarks>
        /// 頂点データを内部の型にかかわらずfloatの値として取得します。
        /// データ型が<dic>byte</dic>,<dic>short</dic>,<dic>int</dic>の場合はその型で表現できる範囲を[-1,1]に正規化した値を返します。
        /// 同様にデータ型が<dic>ushort</dic>,<dic>uint</dic>の場合はその型で表現できる範囲を[0,1]に正規化した値を返します。
        /// <dic>float</dic>型の場合は常に加工無しにそのままの値を取得します。
        /// </remarks>
        /// <param name="firstVertex">取得を始める頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="dst">結果を書き込むfloatの配列</param>
        public override void Get (int firstVertex, int numVertices, float[] dst) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (dst == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (dst.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Dst length is too short");
            }

            for (int i = 0; i < numVertices * ComponentCount; i++) {
                switch (typeof (TValue).Name) {
                    case "Byte": dst[i] = Normalize ((float)(byte)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "SByte": dst[i] = Normalize ((float)(sbyte)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Int16": dst[i] = Normalize ((float)(short)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "UInt16": dst[i] = Normalize ((float)(ushort)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Int32": dst[i] = Normalize ((float)(int)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "UInt32": dst[i] = Normalize ((float)(uint)(ValueType)buffer[firstVertex * ComponentCount + i]); break;
                    case "Single": dst[i] = (float)(ValueType)buffer[firstVertex * ComponentCount + i]; break;
                    default: throw new NotImplementedException("Sorry");
                }
            }
        }
        */

        /// <summary>
        /// 頂点データを書き込みます
        /// </summary>
        /// <param name="firstVertex">書き込みを始める最初の頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="src">頂点データの配列</param>
        public void Set (int firstVertex, int numVertices, TValue[] src) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (src == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (src.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Src length is too short");
            }

            Array.Copy (src, 0, this.buffer, firstVertex * ComponentCount, numVertices * ComponentCount);
        }

        public override void Set (int firstVertex, int numVertices, IntPtr src) {

       
            throw new NotImplementedException ();
        }

        /// <summary>
        /// 頂点データをfloatでセットします
        /// </summary>
        /// <remarks>
        /// <dic>byte</dic>,<dic>short</dic>,<dic>int</dic>型の場合はデータを[-1,1]の範囲で指定します。
        /// <dic>ushort</dic>,<dic>uint</dic>型の場合はデータを[0,1]の範囲で指定します。
        /// これらの値は内部に保存されるときに自動的にその型で表現できる[最小値,最大値]にマップされます。
        /// <dic>float</dic>の場合はそのまま加工せずに保存されます。
        /// </remarks>
        /// <param name="firstVertex">書き込みを始める頂点インデックス</param>
        /// <param name="numVertices">頂点数</param>
        /// <param name="src">データ元のfloatの配列</param>
        /*
        public override void Set (int firstVertex, int numVertices, float[] src) {
            if (firstVertex < 0 || firstVertex >= VertexCount) {
                throw new ArgumentException ("First vertex is invalid");
            }
            if (numVertices < 0 || firstVertex + numVertices > VertexCount) {
                throw new ArgumentException ("Number of vertex is invalid");
            }
            if (src == null) {
                throw new ArgumentNullException ("Dst is null");
            }
            if (src.Length < numVertices * ComponentCount) {
                throw new ArgumentException ("Dst length is too short");
            }

            for (int i = 0; i < numVertices * ComponentCount; i++) {
                switch (typeof (TValue).Name) {
                    case "Byte": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(byte)Expand (src[i]); break;
                    case "SByte": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(sbyte)Expand (src[i]); break;
                    case "Int16": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(short)Expand (src[i]); break;
                    case "UInt16": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(ushort)Expand (src[i]); break;
                    case "Int32": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(int)Expand (src[i]); break;
                    case "UInt32": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)(uint)Expand (src[i]); break;
                    case "Single": this.buffer[firstVertex * ComponentCount + i] = (TValue)(ValueType)src[i]; break;
                    default: throw new NotImplementedException("Sorry");
                }
            }
        }
         * */

        /// <summary>
        /// <typeparamref name="TValue"/>型の[最小値,最大値]を[0,1]もしくは[-1,1]にマップします
        /// </summary>
        /// <remarks>
        /// ただしfloat型の場合は何もしません。
        /// </remarks>
        /// <param name="t"><typeparamref name="TValue"/>型の値</param>
        /// <returns>正規化された値</returns>
        float Normalize (float t) {
            var max = 0f;
            var min = 0f;
            switch (typeof (TValue).Name) {
                case "Byte": max = Byte.MaxValue; min = Byte.MinValue; break;
                case "SByte": max = SByte.MaxValue; min = SByte.MinValue; break;
                case "Int16": max = Int16.MaxValue; min = Int16.MinValue; break;
                case "UInt16": max = UInt16.MaxValue; min = UInt16.MinValue; break;
                case "Int32": max = Int32.MaxValue; min = Int32.MinValue; break;
                case "UInt32": max = UInt32.MaxValue; min = UInt32.MinValue; break;
                case "Single": max = 1; min = 0;break ;
                default: throw new NotImplementedException("Sorry");
            }
            // Unsigned型(0,1)
            if (min >= 0) {
                t = t / (max - min) - min / (max - min);
            }
            // Signed型(-1,1)
            if (min < 0) {
                t = t * 2 / (max - min) - (max + min) / (max - min);
            }
            return t;
        }

        /// <summary>
        /// [0,1]もしくは[-1,1]を<typeparamref name="TValue"/>型の[最小値,最大値]にマップします
        /// </summary>
        /// <remarks>
        /// ただしfloat型の場合は何もしません。
        /// </remarks>
        /// <param name="t">正規化された値</param>
        /// <returns><typeparamref name="TValue"/>型で表せる[最小値,最大値]</returns>
        float Expand (float t) {
            var max = 0f;
            var min = 0f;
            switch (typeof (TValue).Name) {
                case "Byte": max = Byte.MaxValue; min = Byte.MinValue; break;
                case "SByte": max = SByte.MaxValue; min = SByte.MinValue; break;
                case "Int16": max = Int16.MaxValue; min = Int16.MinValue; break;
                case "UInt16": max = UInt16.MaxValue; min = UInt16.MinValue; break;
                case "Int32": max = Int32.MaxValue; min = Int32.MinValue; break;
                case "UInt32": max = UInt32.MaxValue; min = UInt32.MinValue; break;
                case "Single": max = 1; min = 0; break;
                default: throw new NotImplementedException("Sorry");
            }
            // Unsigned型(0,1)
            if (min >= 0) {
                t = t * (max - min) - min;
            }
            // Signed型(-1,1)
            if (min < 0) {
                t = t * (max - min) / 2.0f - (max + min) / 2.0f;
            }

            return t;
        }


        /// <summary>
        /// 現在のオブジェクトの状態を表す文字列を返します。
        /// </summary>
        /// <returns>現在のオブジェクトを説明する文字列</returns>
        public override string ToString () {
            return string.Format ("VertexArray: Vertices={0}, Components={1}, Type={2}", VertexCount, ComponentCount, ComponentType.ToString ());
        }
        #endregion

    }
}
